/*______________________________________________________________________________
 *
 * Copyright 2005 NORSYS
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * (1) Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 * (2) Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *
 * (3) The name of the author may not be used to endorse or promote
 *     products derived from this software without specific prior
 *     written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *______________________________________________________________________________
 *
 * Created on 27 sept. 2005
 * Author: Arnaud Bailly
 */
package speculoos.jndi;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import speculoos.core.Mapper;
import speculoos.core.MapperCollector;
import speculoos.core.MapperException;
import speculoos.jndi.mappers.JNDIMapper;
import speculoos.manager.MapperConfigurationException;
import speculoos.spi.MapperTechnicalException;
import speculoos.spi.Source;
import speculoos.utils.VariableSubstitution;

/**
 * Base implementation for accessing JNDI/LDAP data source.
 * <p>
 * This base implementation uses on demand binding to directory context for
 * providing connections to mappers. Subclasses may override this behavior by
 * providing their own implementations of {@link #connect()} and
 * {@link #close(DirContext)}: The former provides DirContext instance for use
 * by the mapper while the latter releases this instance when it is released by
 * the mapper.
 * </p>
 * <p>
 * Methods {@link #create(String, Map)} and {@link #release(Mapper)} are
 * synchronized to provide minimal thread isolation. Please note howerver that
 * {@link speculoos.core.Mapper} instances are not supposed to be thread safe so
 * it is up to the client to ensure proper concurrency management.
 * </p>
 * <p>
 * Method {@link #add(String, JNDIMapper)} associates a symbolic name to a
 * Mapper instance. This instance will then be used as <em>Prototype</em>
 * object when JNDISource will be asked for creating a Mapper through method
 * {@link #create(String, Map)}: Base object is <code>cloned</code> and
 * initialized with a connection before being returned to the client. The method
 * {@link #release(Mapper)} must be invoked at some later point in order for
 * this source to release associated resources.
 * </p>
 * <p>
 * Environment management is provided through the method
 * {@link #addParameters(Map)} which allows incremental configuration of the
 * environment this source operates in.
 * </p>
 * 
 * @author nono
 * @version $Id: JNDISource.java 76 2005-10-26 14:12:23 +0200 (Wed, 26 Oct 2005)
 *          /C=FR/ST=Nord/L=Lille/O=Norsys SA/OU=UE/CN=Arnaud
 *          Bailly/emailAddress=abailly@norsys.fr $
 */
public class JNDISource implements Source, MapperCollector {

	private static final Log log = LogFactory.getLog(JNDISource.class);

	/* are we bound ? */
	private boolean bound;

	/* environment for variables substitution */
	private Map parameters = new HashMap();

	/* mappers map */
	private Map mapperMatrix = new HashMap();

	private VariableSubstitution subst = new VariableSubstitution();

	/* current environment */
	private Map current;

	private String name;

	/*
	 * store unassigned mappers - used in chains
	 */
	private List mappers = new ArrayList();

	/**
	 * Create a new named JNDISource.
	 * 
	 * @param name
	 *            the name of the source. May not be null.
	 */
	public JNDISource(String name) {
		this.name = name;
	}

	/**
	 * Default constructor for creating anonymous source.
	 * 
	 */
	public JNDISource() {
		this("");
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see speculoos.core.core.spi.Source#start(java.util.Map)
	 */
	public void start(Map env) throws MapperConfigurationException {
		/* instanciate parameters */
		this.current = new HashMap(parameters);
		/* instanciate this environment */
		for (Iterator i = this.current.entrySet().iterator(); i.hasNext();) {
			Map.Entry me = (Map.Entry) i.next();
			String v = subst.replaceVars(env, (String) me.getValue());
			me.setValue(v);
		}
		if (log.isDebugEnabled())
			log.debug("[" + name + "] starting with environment " + current);
		if (log.isInfoEnabled())
			log.info("[" + name + "] started");
		bound = true;
	}

	/**
	 * Create a context from this source. This method simply calls creation of a
	 * new initial context. It may be overriden in subclasses to offer more
	 * sophisticated connection handling (eg. pooling).
	 * 
	 * @return a DirContext representing base node for operation on this source.
	 * @throws MapperTechnicalException
	 */
	protected JNDIConnection connect() throws MapperTechnicalException {
		try {
			return new SimpleJNDIConnectionImpl(new InitialDirContext(new Hashtable(current)));
		} catch (NamingException e) {
			throw new MapperTechnicalException(
					"Error while trying to connect to source ", e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see speculoos.core.core.spi.Source#stop()
	 */
	public void stop() {
		bound = false;
		current = null;
		if (log.isInfoEnabled())
			log.info("[" + name + "] stopped");
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see speculoos.core.core.spi.Source#create(String, java.util.Map)
	 */
	public synchronized Mapper create(String m, Map environment)
			throws MapperException {
		if (!bound)
			throw new IllegalStateException(
					"This source is not opened for business. Come back later");
		JNDIMapper base = (JNDIMapper) mapperMatrix.get(m);
		if (base == null)
			throw new IllegalArgumentException("Mapper named " + m
					+ " is unknown");
		JNDIMapper jm = (JNDIMapper) (base).clone();
		/* sets the directory object to use for operations */
		JNDIConnection dirc;
		if (log.isDebugEnabled())
			log.info("[" + name + "] creating mapper " + m);
		try {
			dirc = connect();
			jm.setDirectory(dirc);
		} catch (NamingException e) {
			throw new MapperTechnicalException(
					"Cannot instantiate mapper " + m, e);
		}
		/* sets the environment */
		Map env = new HashMap(environment);
		env.putAll(current);
		jm.setEnvironment(env);
		if (log.isDebugEnabled())
			log.info("[" + name + "] created mapper " + m + " with env " + env);
		return jm;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see speculoos.core.core.spi.Source#release(speculoos.core.core.Mapper)
	 */
	public void release(Mapper mapper) throws MapperException {
		JNDIMapper jm = (JNDIMapper) mapper;
		if (!mapperMatrix.containsKey(jm.getName()))
			throw new MapperException("[" + name + "] unknown mapper " + jm
					+ " in this source ");
		jm.getDirectory().close();
		/* clean state of object */
		jm.clearDirectory();
		/* clear environment */
		jm.getEnvironment().clear();
		if (log.isDebugEnabled())
			log.info("[" + name + "] released mapper " + mapper.getName());
	}


	/**
	 * Adds a new mapper to this source with given identifier. This method
	 * automatically adds all validation rules in this source to given mapper.
	 * 
	 * @param string
	 *            identifier must be unique within this source or else previous
	 *            mapper is replaced by this one.
	 * @param jm
	 *            the mapper
	 */
	public void add(String string, JNDIMapper jm) {
		mapperMatrix.put(string, jm);
		jm.getInputChain().addMappers(mappers);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see speculoos.spi.Source#addParameters(java.util.Map)
	 */
	public void addParameters(Map env2) {
		this.parameters.putAll(env2);
	}

	/**
	 * Returns the name of this source.
	 * 
	 * @return name of the source.
	 */
	public String getName() {
		return name;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see speculoos.core.MapperCollector#addMapper(speculoos.core.Mapper)
	 */
	public void addMapper(Mapper m) {
		mappers.add(m);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see speculoos.core.MapperCollector#addMappers(java.util.Collection)
	 */
	public void addMappers(Collection col) {
		mappers.addAll(col);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see speculoos.core.MapperCollector#getMappers()
	 */
	public Collection getMappers() {
		return Collections.unmodifiableList(mappers);
	}

	/*
	 * Returns current environment. For internal use only.
	 */
	protected Map getCurrent() {
		return current;
	}

}
